// NSChartCtrl.cpp : implementation file
//
// NSChartCtrl is a MFC chart control. 
//
// Written by Ivan Cachicatari (ivancp@viabcp.com)
// Copyright (c) 2004.
//
// This code may be used in compiled form in any way you desire. This
// file may be redistributed unmodified by any means PROVIDING it is 
// not sold for profit without the authors written consent, and 
// providing that this notice and the authors name is included. If 
// the source code in  this file is used in any commercial application 
// then a simple email would be nice.
//
// This file is provided "as is" with no expressed or implied warranty.
// The author accepts no liability if it causes any damage to your
// computer, causes your pet cat to fall ill, increases baldness or
// makes you car start emitting strange noises when you start it up.
//
// Expect bugs.
// 
// Please use and enjoy. Please let me know of any bugs/mods/improvements 
// that you have found/implemented and I will fix/incorporate them into this
// file. 
//
// Updated 02 Aug 2004
//         
// Version History
// ----------------
//	1.0.1	25 Jun 2004
//			- Initial release.
//
//	1.1.0	02 Aug 2004
//			- Fix some painting problems.
//			- Added HitTest().
//			- Added ModifyItem().
//			- Added DeleteItem().
//			- Added the double buffering paint
//			- Added Notify functions and _NMCHARTCTRL structure



#include "stdafx.h"
#include "NSChartCtrl.h"
#include "math.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

/////////////////////////////////////////////////////////////////////////////
// CNSChartCtrl

CNSChartCtrl::CNSChartCtrl()
{
	RegisterWndClass(AfxGetInstanceHandle());
	m_txtFont.CreateStockObject(DEFAULT_GUI_FONT);
	m_boldFont.CreateStockObject(DEFAULT_GUI_FONT);
	
	LOGFONT lf;
	if(m_txtFont.GetLogFont(&lf)){
		lf.lfWeight = FW_BOLD;
		lf.lfHeight = 15;
		strcpy(lf.lfFaceName,"Arial");
		m_titleFont.DeleteObject();
		m_titleFont.CreateFontIndirect(&lf);
	}

	if(m_boldFont.GetLogFont(&lf)){
		lf.lfWeight = FW_BOLD;
		m_boldFont.DeleteObject();
		m_boldFont.CreateFontIndirect(&lf);
	}

	m_dTotal = 0.0L;
	m_dMax = 0.0l;
	m_dwStyle = NSCS_PIE;
	m_btValFormat = NSCVAL_PERCENT;	// precent
	m_bGridline	= FALSE;

	m_iCurSel = -1;
	m_iMovSel = -1;
	m_CurPoint.x = 0;
	m_CurPoint.y = 0;
}

CNSChartCtrl::~CNSChartCtrl()
{
	m_txtFont.DeleteObject();
	m_boldFont.DeleteObject();

	ResetChart();
	ResetColors();	

	if(m_bmpScreen.m_hObject != NULL) {
		m_bmpScreen.DeleteObject();
		m_bmpScreen.m_hObject = NULL;
	}
}


BEGIN_MESSAGE_MAP(CNSChartCtrl, CWnd)
	//{{AFX_MSG_MAP(CNSChartCtrl)
	ON_COMMAND(ID_EXPORT_NS, OnEcportToFile)
	ON_NOTIFY_EX( TTN_NEEDTEXT, 0, SetTipText )
	ON_WM_PAINT()
	ON_WM_LBUTTONDOWN()
	ON_WM_RBUTTONDOWN()
	ON_WM_ERASEBKGND()
	ON_WM_MOUSEMOVE()
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()


/////////////////////////////////////////////////////////////////////////////
// CNSChartCtrl message handlers

BOOL CNSChartCtrl::RegisterWndClass(HINSTANCE hInstance)
{
	WNDCLASS wc;
	wc.lpszClassName = "MFC_NSChartCtrl"; 
	wc.hInstance = hInstance;
	wc.lpfnWndProc = ::DefWindowProc;
	wc.hCursor = ::LoadCursor(NULL, IDC_ARROW);
	wc.hIcon = 0;
	wc.lpszMenuName = NULL;
	wc.hbrBackground = (HBRUSH) ::GetStockObject(WHITE_BRUSH);
	wc.style = CS_GLOBALCLASS;
	wc.cbClsExtra = 0;
	wc.cbWndExtra = 0;

	return (::RegisterClass(&wc) != 0);
}

void CNSChartCtrl::OnPaint() 
{
	CPaintDC dc(this); 

	int iValues	= m_ChartValues.GetSize();
	int iColors		= m_BrushArray.GetSize();

	CString str;
	CRect rect;
	GetClientRect(&rect);

	// Check values count
	if(iValues <= 0 )
	{
		CFont* oldFont = dc.SelectObject(&m_txtFont);
		str = "No values now"; //"Values count must be > 0";
		dc.FillSolidRect(rect,RGB(255,255,255));
		rect.top += 50;
		dc.DrawText(str,&rect,DT_CENTER|DT_VCENTER);
		dc.SelectObject(oldFont);
		return;
	}
	// Check color count
	if(iColors <= 1)
	{
		CFont* oldFont = dc.SelectObject(&m_txtFont);
		str = "Color count mus be > 1";
		dc.FillSolidRect(rect,RGB(255,255,255));
		rect.top += 50;
		dc.DrawText(str,&rect,DT_CENTER|DT_VCENTER);
		dc.SelectObject(oldFont);
		return;
	}

	//Creating double buffer painting

	CDC imageDC;
	imageDC.CreateCompatibleDC(&dc);

	CBitmap* pOldMemDCBitmap = NULL;

	if(m_bmpScreen.m_hObject == NULL) 
		m_bmpScreen.CreateCompatibleBitmap( &dc, rect.Width(), rect.Height() );
	pOldMemDCBitmap = (CBitmap*)imageDC.SelectObject(&m_bmpScreen);
	
	imageDC.FillSolidRect(rect,RGB(255,255,255));

//	rect.DeflateRect(0,5);

	// Drawing the chart
	if(m_dwStyle & NSCS_BAR ){
		DrawBarChart(&imageDC);
	}
	if(m_dwStyle & NSCS_PIE ){
		DrawPieChart(&imageDC);
	}

	// Drawing the Title
	CFont* oldFont = imageDC.SelectObject(&m_titleFont);
	if( m_csTitle.IsEmpty() ) 
	{
		GetWindowText(str);
		imageDC.DrawText(str,rect,DT_CENTER);
	}
	else
		imageDC.DrawText( m_csTitle, rect, DT_CENTER); // modified by Cjs
	imageDC.SelectObject(oldFont);

	// Drawing the new bitmap

	dc.BitBlt( rect.left , rect.top , rect.Width(), rect.Height(), 
					&imageDC, 0, 0, SRCCOPY );

	imageDC.SelectObject(pOldMemDCBitmap);

}


//
// Cjs modified
//------------------------------------------------------------
void CNSChartCtrl::DrawPieChart(CDC *pDC)
{
	ASSERT(m_dTotal > 0.0L);

	double pi	= 3.1415926535*2;
	int	textrc_div = 40;	// cjs added
	int iValues	= m_ChartValues.GetSize();
	int iColors	= m_BrushArray.GetSize();

	CNSChartValue* pTmp;
	CString str;
	CRect  rect;
	GetClientRect(&rect);

	if( rect.bottom < 200 || rect.right < 250 ) 
		textrc_div = 50;
	
	double dAngle = 0;
	double dMidAngle = 0;
	double r = min((rect.Width()/2)-50,(rect.Height()/2)-40);
	CPoint p1 = rect.CenterPoint();		// Center poiner of Ellipse
	p1.y += 15;
	CPoint p2(p1.x + (int)r ,p1.y);
	CPoint p3;
	CPoint p4;

	pDC->Ellipse(p1.x-(int)r,p1.y-(int)r,p1.x+(int)r,p2.y+(int)r);
	pDC->MoveTo(p1);
	pDC->LineTo(p2);
	CFont *oldFont = pDC->SelectObject(&m_txtFont);

	for (int i = 0; i < iValues; i++ )
	{
		pTmp	= m_ChartValues.GetAt(i);
		dMidAngle = pTmp->m_dValue * pi / m_dTotal;
		
		dAngle += dMidAngle;
		p2.x = (int)(cos(dAngle)*r + p1.x);
		p2.y = (int)(p1.y - sin(dAngle)*r ); // part separate point

		pDC->MoveTo(p1);
		pDC->LineTo(p2);	// part separate line

		// blow draw line of label
		dMidAngle = dAngle - dMidAngle/2;
		p3.x = (int)(cos(dMidAngle)*r + p1.x);
		p3.y = (int)(p1.y - sin(dMidAngle)*r );

		p4.x = (int)(cos(dMidAngle)*(r*1.2) + p1.x);
		p4.y = (int)(p1.y - sin(dMidAngle)*(r*1.2) );

		pDC->MoveTo(p3);	
		pDC->LineTo(p4);
		pDC->MoveTo(p4);
		p4.x += textrc_div*((p4.x > p1.x)?(1):(-1));
		pDC->LineTo(p4);

		// text rect
		rect.left = p4.x - 30;
		rect.right = p4.x + 30;
		rect.top = p4.y - 15;
		rect.bottom = p4.y + 15;
//		pDC->Rectangle( &rect ); // For trance only
		
		str.Format(_T("%s\n%.1f%%"),pTmp->m_sLabel,(pTmp->m_dValue*100.0)/m_dTotal);
		if(m_iCurSel == i)
		{
			CFont *tmpFont = pDC->SelectObject(&m_boldFont);
			rect.InflateRect(2,0);
			pDC->DrawText(str,&rect,DT_LEFT|DT_VCENTER);
			pDC->SelectObject(tmpFont);
		}else{
			pDC->DrawText(str,&rect,DT_LEFT|DT_VCENTER);
		} 

		// fill area
		p4.x = (int)(cos(dMidAngle)*(r*0.8) + p1.x);
		p4.y = (int)(p1.y - sin(dMidAngle)*(r*0.8) );

		if(pDC->GetPixel(p4) > 0x0)
		{
			CBrush* tmpBr = pDC->SelectObject((CBrush*)m_BrushArray.GetAt(i%iColors));
			pDC->FloodFill(p4.x,p4.y,0x0);
			pDC->SelectObject(tmpBr);
		}
	}

	pDC->SelectObject(oldFont);
}

#define		BAR_MINWIDTH		30
#define		BAR_MAXWIDTH		80
#define		BAR_INTERVAL		4

//
// Cjs modified
//------------------------------------------------------------
void CNSChartCtrl::DrawBarChart(CDC *pDC)
{
	ASSERT(m_dTotal > 0.0L);

	int iValues	= m_ChartValues.GetSize();
	int iColors	= m_BrushArray.GetSize();
	CString str;

	CRect rect,rcBar;
	GetClientRect(&rect);

	CNSChartValue* pTmp;

    double t = 1.0L;
	if(m_dMax >= 1.0L)
	{
		str.Format("%.0f",m_dMax);
		int d = 1;
		int iStrlen = str.GetLength();
		for(int i=0; i<iStrlen-1; i++) 
			d*=10;

		int iInter = (int)m_dMax;
		if( iInter == m_dMax ) 
			t = m_dMax;
		else
		{
			if( (1 == iStrlen) || ((str.GetAt(1)-48) >= 5) ) 
				t = max( ( ((double)str.GetAt(0)-48)+1 ) * d, m_dMax );
			else
				t = ((double)str.GetAt(0)-48)*d + (((double)str.GetAt(1)-48)+1)* d/10;
		}
		
	}
	
	if( m_dTotal >= 1000000.0L ) 
	{
		rect.DeflateRect(50,30,20,20);
		
		rcBar.left   =  1; 
		rcBar.right  = rcBar.left + 50; 
	}
	else if( m_dTotal >= 100000.0L ) 
	{
		rect.DeflateRect(45,30,20,20);
		
		rcBar.left   =  1; 
		rcBar.right  = rcBar.left + 45; 
	}
	else
	{
		rect.DeflateRect(40,30,20,20);
		
		rcBar.left   =  2; 
		rcBar.right  = rcBar.left + 35; 
	}
	rcBar.top    = 25;
	rcBar.bottom = rcBar.top + 15;
	int i = rect.Height();

	CFont *oldFont = pDC->SelectObject(&m_txtFont);

	// Need gridline or not
	INT	iLineEnd = (m_bGridline) ? (rect.right) : (rect.left);
	
	pDC->MoveTo(rect.left - 7,rect.top);
	pDC->LineTo(iLineEnd + 7,rect.top);
	str.Format( (t<10.0) ? ("%.1f") : ("%.0f"), t );
	pDC->DrawText(str,rcBar,DT_RIGHT);
	
	pDC->MoveTo(rect.left - 5,rect.top + i/4);
	pDC->LineTo(iLineEnd + 5,rect.top + i/4);
	str.Format( (t<10.0) ? ("%.1f") : ("%.0f"), t*3/4 );
	rcBar.top    = rect.top + (int)(i/4);
	rcBar.bottom = rcBar.top + 15;
	pDC->DrawText(str,rcBar,DT_RIGHT);
	
	pDC->MoveTo(rect.left - 7,rect.top + i/2);
	pDC->LineTo(iLineEnd + 7,rect.top + i/2);
	str.Format( (t<10.0) ? ("%.1f") : ("%.0f"), t/2 );
	rcBar.top    = rect.top + (int)(i/2);
	rcBar.bottom = rcBar.top + 15;
	pDC->DrawText(str,rcBar,DT_RIGHT);

	pDC->MoveTo(rect.left - 5,rect.top + i*3/4);
	pDC->LineTo(iLineEnd + 5,rect.top + i*3/4);
	str.Format( (t<10.0) ? ("%.1f") : ("%.0f"), t/4 );
	rcBar.top    = rect.top + (int)(i*3/4);
	rcBar.bottom = rcBar.top + 15;
	pDC->DrawText(str,rcBar,DT_RIGHT);
	
	rcBar = rect;
	//rcBar.DeflateRect(0,10,0,0);	// will distortion

	// X line
	pDC->MoveTo(rect.left  -15, rect.bottom);
	pDC->LineTo(rect.right +15, rect.bottom);

	// Y line
	pDC->MoveTo(rect.left , rect.top    -15);
	pDC->LineTo(rect.left , rect.bottom +15);

	int scale = rcBar.Width()/iValues;
	if( scale > BAR_MAXWIDTH ) // check max width
		scale = BAR_MAXWIDTH;
	int height = rcBar.Height();
	CRect tmpRect;
	for ( i = 0; i < iValues; i++ )
	{
		CFont *tmpFont = NULL; // for selected part

		pTmp = m_ChartValues.GetAt(i);
		rcBar.left  = rect.left + scale*i + 2;
		rcBar.right = rcBar.left + scale - BAR_INTERVAL;
		rcBar.top = (rcBar.bottom - (long)(pTmp->m_dValue*height/t));
		rcBar.bottom = rect.bottom+1;

		//Drawing bar's
		CBrush* tmpBr = pDC->SelectObject((CBrush*)m_BrushArray.GetAt(i%iColors));
		pDC->Rectangle(rcBar);
		pDC->SelectObject(tmpBr);

		if(m_iCurSel == i)
			tmpFont = pDC->SelectObject(&m_boldFont);			
		
		// <Cjs> don't display text when Bar width is too small!
		if( (rcBar.right - rcBar.left) < BAR_MINWIDTH ) // compact show
		{	
			//Drawing bottom text labels
			tmpRect = rcBar;
			tmpRect.top		= rcBar.bottom + 2;
			tmpRect.bottom  = tmpRect.top + 15;
			str.Format( "%d", i + 1 );
			pDC->DrawText( str,tmpRect,DT_LEFT);
		}
		else	// normal show
		{
			//Drawing top text percents
			tmpRect = rcBar;
			tmpRect.bottom = tmpRect.top - 1;
			tmpRect.top = tmpRect.bottom - 15;
			if( NSCVAL_PERCENT == m_btValFormat )
				str.Format("%.1f%%",100*pTmp->m_dValue/m_dTotal);
			else
				str.Format("%.2f", pTmp->m_dValue);
			pDC->DrawText(str,tmpRect,DT_BOTTOM|DT_LEFT);

			//Drawing bottom text labels
			tmpRect.top		= rcBar.bottom + 2;
			tmpRect.bottom  = tmpRect.top + 15;
			pDC->DrawText(pTmp->m_sLabel,tmpRect,DT_LEFT);
		}

		if(m_iCurSel == i)
			pDC->SelectObject(tmpFont);
	}

	pDC->SelectObject(oldFont);
}

void CNSChartCtrl::PrepareColors(DefaultColors defaultColor)
{
	switch(defaultColor){
		case EMPY:
			ResetColors();
			break;
		case GrayScale:
			ResetColors();
			AddSolidBrush(0x00666666);
			AddSolidBrush(0x00DDDDDD);
			AddSolidBrush(0x00888888);
			AddSolidBrush(0x00444444);
			AddSolidBrush(0x00CCCCCC);
			AddSolidBrush(0x00AAAAAA);
			//AddSolidBrush(0x00000000);
			break;
		case SimpleColors:
			ResetColors();
			AddSolidBrush(0x0000CC00);
			AddSolidBrush(0x00FF0000);
			AddSolidBrush(0x00CCCC00);
			AddSolidBrush(0x000000FF);
			AddSolidBrush(0x000000CC);
			AddSolidBrush(0x0000FF00);
			AddSolidBrush(0x00FF00FF);
			AddSolidBrush(0x00CC00CC);
			AddSolidBrush(0x00CC0000);
			AddSolidBrush(0x0000FFFF);
			AddSolidBrush(0x0000CCCC);
			AddSolidBrush(0x00FFFF00);

			break;
	};
}

BOOL CNSChartCtrl::AddSolidBrush(COLORREF cr)
{
	CBrush *br = NULL;
	try 
	{
		br = new CBrush(cr);
		m_BrushArray.Add(br);		
		return TRUE;
	}
	catch (CMemoryException* e)
	{
		if (br != NULL) 
		{
			delete br;
		}
		e->Delete();
		return FALSE;
	}
}

// Modified by Cjs 
//--------------------------------------------------------------
void CNSChartCtrl::SetChartStyle(DWORD dStyle)
{
	m_ToolTip.Activate(FALSE); // cjs , only for key operation
	
	m_dwStyle = dStyle;	
	Invalidate(TRUE);
}

void CNSChartCtrl::ResetColors()
{
	int iSize = m_BrushArray.GetSize();
	for (int i = 0; i < iSize; i++ )
	{
		delete m_BrushArray.GetAt(i);
	}
	m_BrushArray.RemoveAll();	
}

BOOL CNSChartCtrl::AddBrush(CBrush *br)
{
	ASSERT(br->m_hObject);

	try 
	{
		m_BrushArray.Add(br);
		return TRUE;
	}
	catch (CMemoryException* e)
	{
		if (br != NULL) 
		{
			delete br;
		}
		e->Delete();
		return FALSE;
	}
}

BOOL CNSChartCtrl::AddValue(DOUBLE dValue, LPCTSTR strLabel,BOOL bRepaint /*= FALSE*/)
{
	if(dValue <= 0)
	{
		TRACE("The double value in function AddValue must be > 0 (dValue = %lf)",dValue);
		return FALSE;
	}

	CNSChartValue* newValue = new CNSChartValue;
	newValue->m_dValue  = dValue;
	newValue->m_sLabel  = strLabel;

	try 
	{
		m_ChartValues.Add(newValue);
		m_dTotal += dValue;
		if(m_dMax < dValue)
		{
			m_dMax = dValue;
		}
		
		if(bRepaint)
		{
			Invalidate();
		}

		m_iCurSel = -1;
		return TRUE;
	}
	catch (CMemoryException* e)
	{
		if (newValue !=NULL) 
		{
			delete newValue;
		}
		e->Delete();
		return FALSE;
	}
}

void CNSChartCtrl::ResetChart()
{
	int iSize = m_ChartValues.GetSize();
	
	for (int i = 0; i < iSize; i++ )
	{
		delete m_ChartValues.GetAt(i);
	}
	m_ChartValues.RemoveAll();
}

void CNSChartCtrl::OnLButtonDown(UINT nFlags, CPoint point) 
{
	static int ilastSel = -2;

	m_iCurSel = HitTest(point);

	Invalidate(FALSE);

	Notify(NSCS_SELECTEDITEM,m_iCurSel);
}


//
// Cjs modified
//------------------------------------------------------------
int CNSChartCtrl::HitTest(CPoint &pt)
{
	if(m_dTotal <= 0.0)
	{
		return -1;
	}


	if(m_dwStyle & NSCS_BAR )
	{

		int iValues	= m_ChartValues.GetSize();

		CRect rect,rcBar;
		CString str;
		GetClientRect(&rect);

		double t = 1.0L;
		if(m_dMax >= 1.0L)
		{
			str.Format("%.0f",m_dMax);
			int d = 1;
			int iStrlen = str.GetLength();
			for(int i = 0;i<iStrlen-1;i++) 
				d*=10;

			int iInter = (int)m_dMax;
			if( iInter == m_dMax ) 
				t = m_dMax;
			else
			{
				if( (1 == iStrlen) || ((str.GetAt(1)-48) >= 5) ) 
					t = max( ( ((double)str.GetAt(0)-48)+1 ) * d, m_dMax );
				else
					t = ((double)str.GetAt(0)-48)*d + (((double)str.GetAt(1)-48)+1)* d/10;
			}
		}

		if( m_dTotal >= 1000000.0L ) 
			rect.DeflateRect(50,30,20,20);
		else if( m_dTotal >= 100000.0L ) 
			rect.DeflateRect(45,30,20,20);
		else
			rect.DeflateRect(40,30,20,20);
		
		rcBar = rect;
		rcBar.bottom = rect.bottom+1;

		int scale = rcBar.Width()/iValues;
		if( scale > BAR_MAXWIDTH ) // check max width
			scale = BAR_MAXWIDTH;
		int height = rcBar.Height();
	
		CNSChartValue* pTmp;
		CRect tmpRect;	// cjs added

		for (int i = 0; i < iValues; i++ )
		{
			pTmp = m_ChartValues.GetAt(i);
			rcBar.left  = rect.left + scale*i + 2;
			rcBar.right = rcBar.left + scale - BAR_INTERVAL;
			rcBar.top = (rcBar.bottom - (long)(pTmp->m_dValue*height/t));

			tmpRect = rcBar;	// cjs added
			tmpRect.top		= rcBar.bottom + 2;
			tmpRect.bottom  = tmpRect.top + 15;
			
			if( rcBar.PtInRect(pt) || tmpRect.PtInRect(pt) )
			{
				return i;
			}
		}

	}
	if(m_dwStyle & NSCS_PIE )
	{
		CRect  rect;
		GetClientRect(&rect);
		double pi	= 3.1415926535;
		double r = min((rect.Width()/2)-50,(rect.Height()/2)-40);
		CPoint p1 = rect.CenterPoint();
		p1.y += 15;

		double x = (pt.x - p1.x);
		double y = (pt.y - p1.y);
		double h = sqrt(x*x + y*y);

		if(h > 0.0 && h < r)
		{
			double dClickAngle = -asin(y/h);
			
			if(p1.x > pt.x) dClickAngle = pi - dClickAngle;
			if(dClickAngle < 0.0) dClickAngle = pi*2 + dClickAngle;
			
			int iValues	= m_ChartValues.GetSize();
			int iColors	= m_BrushArray.GetSize();

			CNSChartValue* pTmp;
			double dCurrAngle = 0;
			double dLastAngle = 0;

			for (int i = 0; i < iValues; i++ )
			{
				pTmp	= m_ChartValues.GetAt(i);
				dCurrAngle = dLastAngle + pTmp->m_dValue * 2*pi / m_dTotal;
				
				if(dClickAngle >= dLastAngle &&  dClickAngle <= dCurrAngle )
				{
					return i;
				}
				dLastAngle = dCurrAngle;
			}
		}
	}
	return -1;
}


BOOL CNSChartCtrl::Notify(UINT nCode, INT iItem)
{
	NMCHARTCTRL	nm;
	nm.hdr.hwndFrom = GetSafeHwnd();
	nm.hdr.idFrom = GetDlgCtrlID();
	nm.hdr.code = nCode;

	nm.iItem = iItem;
	
	if(iItem >= 0 && iItem < m_ChartValues.GetSize())
	{
		CNSChartValue* tmp = m_ChartValues.GetAt(iItem);
		nm.dValue = tmp->m_dValue;
		_tcsncpy(nm.sLabel,tmp->m_sLabel,MAXCHARLABEL+1);
	}else
	{
		_tcsncpy(nm.sLabel,"",MAXCHARLABEL+1);
	}
	
	return Notify(&nm);
}

BOOL CNSChartCtrl::Notify(LPNMCHARTCTRL lpnm)
{
	CWnd* pWnd = GetParent();
	if(pWnd)
	{
		return pWnd->SendMessage(WM_NOTIFY, GetDlgCtrlID(), (LPARAM)lpnm);
	}
	return FALSE;
}

bool CNSChartCtrl::ModifyItem(INT iItem, DOUBLE dValue,CString &str)
{
	if(iItem >= 0 && iItem < m_ChartValues.GetSize())
	{
		CNSChartValue *tmp = m_ChartValues.GetAt(iItem);

		m_dTotal += dValue - tmp->m_dValue;
		tmp->m_dValue = dValue;
		tmp->m_sLabel = str;
		if(m_dMax < dValue)
		{
			m_dMax = dValue;
		}
		Invalidate();
		return true;
	}
	return false;
}

BOOL CNSChartCtrl::OnEraseBkgnd(CDC* pDC) 
{
	
	return TRUE;
}

bool CNSChartCtrl::DeleteItem(INT iItem)
{
	if(iItem >= 0 && iItem < m_ChartValues.GetSize())
	{
		CNSChartValue *rip = m_ChartValues.GetAt(iItem);
		
		m_dTotal = 0;
		m_dMax = 0;
		delete rip;
		m_ChartValues.RemoveAt(iItem);

		CNSChartValue *tmp; 
		int iSize = m_ChartValues.GetSize();
			
		for (int i = 0; i < iSize; i++ )
		{
			tmp = m_ChartValues.GetAt(i);
			if(m_dMax < tmp->m_dValue )
			{
				m_dMax = tmp->m_dValue;
			}
			m_dTotal += tmp->m_dValue;
		}
		m_iCurSel = -1;
		Invalidate();

		return true;
	}
	return false;
}

// Append by Cjs 
//--------------------------------------------------------------
INT CNSChartCtrl::GetItemCount()
{
	return m_ChartValues.GetSize();
}


// Append by Cjs 
// !Note! In the process CObArray::RemoveAt(), it shifts down all 
// the elements above the removed element(s). It decrements 
// the upper bound of the array but does not free memory. 
//--------------------------------------------------------------
void CNSChartCtrl::Empty()
{
	INT		i, Count = m_ChartValues.GetSize();

	for( i=0; i< Count; i++ )
		DeleteItem( 0 ); // see comment note above!
}

// Append by Cjs 
//--------------------------------------------------------------
void CNSChartCtrl::SetTitle( CString &csTitle )
{
	if( !csTitle.IsEmpty() )
		m_csTitle = csTitle;
}

// Append by Cjs 
//--------------------------------------------------------------
DOUBLE CNSChartCtrl::GetItemValue( INT item )
{
	
	if( item >= 0 && item < m_ChartValues.GetSize() )
	{
		CNSChartValue *tmp; 
		tmp = m_ChartValues.GetAt( item );
		return tmp->m_dValue;
	}

	return 0;
}

// Append by Cjs 
//--------------------------------------------------------------
CString CNSChartCtrl::GetItemLabel(INT item)
{

	if( item >= 0 && item < m_ChartValues.GetSize() )
	{
		CNSChartValue *tmp; 
		tmp = m_ChartValues.GetAt( item );
		return tmp->m_sLabel;
	}
	else
	{
		CString	 str( "NULL" );
		return str;
	}
}

// Append by Cjs 
//--------------------------------------------------------------
void CNSChartCtrl::SetPieceValueFormat(unsigned char btFormat)
{
	m_btValFormat = btFormat;
	Invalidate(TRUE);
}

// Append by Cjs 
//--------------------------------------------------------------
void CNSChartCtrl::EnableBarGridLines(BOOL bGridline)
{
	m_bGridline = bGridline;
}


// Append by Cjs 
//--------------------------------------------------------------
BOOL CNSChartCtrl::PreTranslateMessage(MSG* pMsg) 
{
	if( m_ToolTip  ) 
		m_ToolTip.RelayEvent(pMsg);
	
	return CWnd::PreTranslateMessage(pMsg);
}

// 
// Pay attention this !
// Append by Cjs 
//--------------------------------------------------------------
void CNSChartCtrl::PreSubclassWindow() 
{
	EnableToolTips(TRUE);
	
    CRect rect; 
    GetClientRect(rect);
    m_ToolTip.Create(this);
	m_ToolTip.Activate(FALSE);
	
    m_ToolTip.AddTool(this, LPSTR_TEXTCALLBACK, rect, NSTOOLTIP_ID );
	
	CWnd::PreSubclassWindow();
}

// 
// Append by Cjs 
//--------------------------------------------------------------
BOOL CNSChartCtrl::SetTipText( UINT id, NMHDR * pTTTStruct, LRESULT * pResult )
{
	TOOLTIPTEXT *pTTT = (TOOLTIPTEXT *)pTTTStruct;    

	// <> Confirm tooltips info
	if( m_iMovSel > -1 ) 
	{
		CNSChartValue* pTmp;
		pTmp	= m_ChartValues.GetAt( m_iMovSel );
		
		sprintf( pTTT->lpszText, " %s : %.2f(%.1f%%)", pTmp->m_sLabel, pTmp->m_dValue, 100*pTmp->m_dValue/m_dTotal );
	}

	return(FALSE);
}

// 
// Append by Cjs 
//--------------------------------------------------------------
void CNSChartCtrl::OnMouseMove(UINT nFlags, CPoint point) 
{
	int	iSel = -1;

	// <> WM_MOUSEMOVE will be repeated atuo even if mouse don't move
	if( (m_CurPoint.x == point.x) && (m_CurPoint.y == point.y) )
		return ;
	
	m_CurPoint.x = point.x;
	m_CurPoint.y = point.y;

	iSel = HitTest(point);

	if( m_iMovSel != iSel ) 
	{
		m_ToolTip.Activate(FALSE);
		m_iMovSel = iSel;
	}

	if( m_iMovSel > -1 ) 
		m_ToolTip.Activate(TRUE);
	else
		m_ToolTip.Activate(FALSE);
	
	CWnd::OnMouseMove(nFlags, point);
}


// 
// Append by Cjs
//  
// Function: CNSChartCtrl::DDBToDIB()                                                             */
//                                                                                              */
// Purpose : converts a device dependent bitmap to a device independent bitmap                  */
//                                                                                              */
// Inputs  : CBitmap& pBitmap -> bitmap to convert                                              */
//           DWORD dwCompression -> compression to employ                                       */
//           CPalette* pPal -> palette for this bitmap to best make the conversion              */
//                                                                                              */
// Outputs : HANDLE <- handle to the converted bitmap                                           */
//--------------------------------------------------------------
HANDLE CNSChartCtrl::DDBToDIB(CBitmap &pBitmap, DWORD dwCompression, CPalette *pPal)
{
	BITMAP              bm;
	BITMAPINFOHEADER    bi;
	LPBITMAPINFOHEADER 	lpbi;
	DWORD               dwLen;
	HANDLE              hDIB;
	HANDLE              handle;
	HDC                 hDC;
	HPALETTE            hPal;


	ASSERT( pBitmap.GetSafeHandle() );

	// The function has no arg for bitfields
	if(dwCompression==BI_BITFIELDS)
		return NULL;

	// If a palette has not been supplied use defaul palette
	hPal = (HPALETTE)pPal->GetSafeHandle();
	
    if (!hPal)
		hPal = (HPALETTE)GetStockObject(DEFAULT_PALETTE);

	// Get bitmap information
	pBitmap.GetObject(sizeof(bm),(LPSTR)&bm);

	// Initialize the bitmapinfoheader
	bi.biSize		= sizeof(BITMAPINFOHEADER);
	bi.biWidth		= bm.bmWidth;
	bi.biHeight 		= bm.bmHeight;
	bi.biPlanes 		= 1;
	bi.biBitCount		= bm.bmPlanes * bm.bmBitsPixel;
	bi.biCompression	= dwCompression;
	bi.biSizeImage		= 0;
	bi.biXPelsPerMeter	= 0;
	bi.biYPelsPerMeter	= 0;
	bi.biClrUsed		= 0;
	bi.biClrImportant	= 0;

	// Compute the size of the  infoheader and the color table
	int nColors = (1<<bi.biBitCount);
	if (nColors>256)
		nColors = 0;

	dwLen = bi.biSize + nColors * sizeof(RGBQUAD);

	// We need a device context to get the DIB from
    hDC = ::GetDC(NULL);
	hPal = SelectPalette(hDC, hPal, FALSE);
	RealizePalette(hDC);

	// Allocate enough memory to hold bitmapinfoheader and color table
	hDIB = GlobalAlloc(GMEM_FIXED, dwLen);

	if(!hDIB) 
	{
		SelectPalette(hDC, hPal, FALSE);
        ::ReleaseDC(NULL, hDC);
		return NULL;
    }

	lpbi = (LPBITMAPINFOHEADER)hDIB;

	*lpbi = bi;

	// Call GetDIBits with a NULL lpBits param, so the device driver 
	// will calculate the biSizeImage field 
	GetDIBits(hDC, (HBITMAP)pBitmap.GetSafeHandle(), 0L, (DWORD)bi.biHeight, (LPBYTE)NULL, (LPBITMAPINFO)lpbi, (DWORD)DIB_RGB_COLORS);

	bi = *lpbi;

	// If the driver did not fill in the biSizeImage field, then compute it
	// Each scan line of the image is aligned on a DWORD (32bit) boundary
	if(bi.biSizeImage == 0) 
	{
		bi.biSizeImage = ((((bi.biWidth * bi.biBitCount) + 31) & ~31) / 8) * bi.biHeight;

		// If a compression scheme is used the result may infact be larger
		// Increase the size to account for this.
		if (dwCompression != BI_RGB)
			bi.biSizeImage = (bi.biSizeImage * 3) / 2;
    }

	// Realloc the buffer so that it can hold all the bits
	dwLen += bi.biSizeImage;
	if( handle = GlobalReAlloc(hDIB, dwLen, GMEM_MOVEABLE) )
		hDIB = handle;
	else 
	{
		GlobalFree(hDIB);

		// Reselect the original palette
		SelectPalette(hDC, hPal, FALSE);
		::ReleaseDC(NULL, hDC);
		return NULL;
    }

	// Get the bitmap bits
	lpbi = (LPBITMAPINFOHEADER)hDIB;

	// FINALLY get the DIB
	BOOL bGotBits = GetDIBits( hDC, (HBITMAP)pBitmap.GetSafeHandle(),
				0L,				// Start scan line
				(DWORD)bi.biHeight,		// # of scan lines
				(LPBYTE)lpbi 			// address for bitmap bits
				+ (bi.biSize + nColors * sizeof(RGBQUAD)),
				(LPBITMAPINFO)lpbi,		// address of bitmapinfo
				(DWORD)DIB_RGB_COLORS);		// Use RGB for color table

	if(!bGotBits) 
	{
		GlobalFree(hDIB);
		
		SelectPalette(hDC, hPal, FALSE);
		::ReleaseDC(NULL, hDC);
		return NULL;
    }

	SelectPalette(hDC, hPal, FALSE);
	
    ::ReleaseDC(NULL, hDC);
	
    return hDIB;
}


// 
// Append by Cjs 
// Function: CNSChartCtrl::WriteDIB()                                                             */
//                                                                                              */
// Purpose : writes a DIB to a file                                                             */
//                                                                                              */
// Inputs  : CString* pFileName -> file name for storage                                        */
//           HANDLE hDIB -> handle to the bitmap                                                */
//                                                                                              */
// Outputs : BOOL <- TRUE if fails                                                              */
//--------------------------------------------------------------
BOOL CNSChartCtrl::WriteDIB( CString *pFileName, HANDLE hDIB )
{
    BITMAPFILEHEADER	hdr;
	LPBITMAPINFOHEADER	lpbi;
	

	if( !hDIB )
		return TRUE;
	
	CFile file;
	if( !file.Open(*pFileName, CFile::modeWrite|CFile::modeCreate) )
		return TRUE;
	
	lpbi = (LPBITMAPINFOHEADER)hDIB;
	
	int nColors = 1 << lpbi->biBitCount;
	
	// Fill in the fields of the file header 
	hdr.bfType      = ((WORD) ('M' << 8) | 'B');	// is always "BM"
	hdr.bfSize      = GlobalSize (hDIB) + sizeof( hdr );
	hdr.bfReserved1 = 0;
	hdr.bfReserved2 = 0;
	hdr.bfOffBits   = (DWORD) (sizeof( hdr ) + lpbi->biSize + nColors * sizeof(RGBQUAD));
	
	// Write the file header 
	file.Write( &hdr, sizeof(hdr) );
	
	// Write the DIB header and the bits 
	file.Write( lpbi, GlobalSize(hDIB) );
	
	return FALSE;
}

// 
// Append by Cjs 
// Function: CNSChartCtrl::ExportBMPToFile()                                                      */
//                                                                                              */
// Purpose : exports the chart as an image to a .bmp file                                       */
//                                                                                              */
// Inputs  : CString* pFileName -> file name for new .bmp image                                 */
//                                                                                              */
// Outputs : BOOL <- TRUE if fails                                                              */
//--------------------------------------------------------------
BOOL CNSChartCtrl::ExportBMPToFile(CString *pFileName)
{
	CDC			imageDC;
    CClientDC	dc(this);
	CBitmap     EntirePlotBitmap;
	CBitmap	   *pOldMemDCBitmap = NULL;
	CString		str;
	CRect		rect;
	

    // prepare the bitmap for the entire plot
	GetClientRect( &rect );
	
	imageDC.CreateCompatibleDC( &dc );
	EntirePlotBitmap.CreateCompatibleBitmap( &dc, rect.Width(), rect.Height() );
	pOldMemDCBitmap = (CBitmap*)imageDC.SelectObject( &EntirePlotBitmap );
	
	// clear the client
	imageDC.FillSolidRect( rect, RGB(255,255,255) );
	
	// Drawing the chart
	if(m_dwStyle & NSCS_BAR ){
		DrawBarChart(&imageDC);
	}
	if(m_dwStyle & NSCS_PIE ){
		DrawPieChart(&imageDC);
	}
	
	// Drawing the Title
	CFont* oldFont = imageDC.SelectObject(&m_titleFont);
	if( m_csTitle.IsEmpty() ) 
	{
		GetWindowText(str);
		imageDC.DrawText( str, rect, DT_CENTER );
	}
	else
		imageDC.DrawText( m_csTitle, rect, DT_CENTER); // modified by Cjs
	imageDC.SelectObject(oldFont);
	
	
    HANDLE hDIB = DDBToDIB( EntirePlotBitmap, BI_RGB, imageDC.GetCurrentPalette() );
	
	imageDC.SelectObject( pOldMemDCBitmap );
	
    if( hDIB )
        return WriteDIB( pFileName, hDIB );
    else
        ASSERT(FALSE);


    return TRUE;
}


// 
// Append by Cjs 
// Popup right menu
//--------------------------------------------------------------
void CNSChartCtrl::OnRButtonDown(UINT nFlags, CPoint point)
{
    CMenu	popupMenu;
 
	popupMenu.CreatePopupMenu();

    CString String("Export chart");
    popupMenu.AppendMenu(MF_ENABLED, ID_EXPORT_NS, String);
    popupMenu.AppendMenu(MF_ENABLED, MF_SEPARATOR);
    String = "Print chart";
    popupMenu.AppendMenu(MF_GRAYED, ID_PRINT_NS, String);
    
    // Get the window position
    CRect rect;
    GetWindowRect( &rect );
    popupMenu.TrackPopupMenu( TPM_LEFTALIGN|TPM_RIGHTBUTTON, rect.left + point.x, rect.top + point.y, this, NULL );
	
    CWnd::OnRButtonDown( nFlags, point );
	
    return ;
}


// 
// Append by Cjs 
// 
//--------------------------------------------------------------
void CNSChartCtrl::OnEcportToFile() 
{
	CHAR	chPath[256] = { 0 };

	if( !m_csTitle.IsEmpty() ) 
		strcpy( chPath, m_csTitle );
	else
		strcpy( chPath, "Untitled" );

	// Must include OFN_NOCHANGEDIR here, otherwise the changed path will occur fstream:open() error!
	CFileDialog Dlg( FALSE, "bmp", chPath, OFN_HIDEREADONLY|OFN_OVERWRITEPROMPT|OFN_NOCHANGEDIR, "Bitmap Files (*.bmp)|*.bmp||", this );
	
	Dlg.m_ofn.lpstrTitle = "Save chart as";
	
	if( IDOK==Dlg.DoModal() )
		ExportBMPToFile( &Dlg.GetPathName() );

	
	
	return ;
}
